﻿package  igz.fleaxy.net
{
	//import com.greensock.TweenMax;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.errors.IOError;
	import flash.events.Event;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.external.ExternalInterface;
	import flash.net.FileReference;
	import flash.net.navigateToURL;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLVariables;
	import flash.net.URLRequestMethod;
	import flash.system.System;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import igz.fleaxy.events.CommEvent;
	import igz.fleaxy.util.ObjectUtil;
	import mx.utils.Base64Decoder;
	 
	/**
	 * @author Intelygenz
	 */
	public class Comm {
		//static private var _PostCount:int = 0;

		static private var _Calls : Array = new Array();

		private var _Uri:String;
		private var _URLLoader:URLLoader;
		private var _Loader:Loader;

		private var _Call_OnComplete : Function;
		private var _Call_OnIOError  : Function;
		private var _Call_OnProgress : Function;
		private var _Call_OnHttpStatus : Function;
		private var _Params : Object;
		
		private var _MultipartBoundary : String;

		public function Comm() {
			
		}
		
		
		/**
		 * Abre una nueva ventana del navegador con la url indicada.
		 * Si existe la funcion NavigateToURL en la página la usa, en caso contrario usa "flash.net.navigateToURL()"
		 * function NavigateToURL (p_url,p_winCd,p_winParams) {
		 *   if ( p_url.indexOf("mailto:")==0 ) {
		 * 	   document.location.href = p_url;
		 *   }
		 *   else {
		 * 	   window.open( p_url, p_winCd, p_winParams );
		 *   }
		 * }
		 * 
		 * @param	p_url Url destino
		 */
		static public function $NavigateToURL ( p_url:String, p_winCd: String="", p_winParams:String="" ) : void {
			if ( ExternalInterface.available ) {
				ExternalInterface.call( "NavigateToURL", p_url, p_winCd, p_winParams );
			}
			else {
				navigateToURL( new URLRequest( p_url ) );
			}
		}
		
		/**
		 * Realiza una petición GET a la dirección indicada.
		 * Al terminar correctamente se ejecuta la función onComplete(p_responseListeners) pasándola como parámetro un objeto CommEvent.
		 * A partir de él podemos acceder al texto ($ResponseText), XML ($ResponseXML) o JSON ($ResponseJSON) devuelto.
		 * 
		 * @param	p_url Dirección URL
		 * @param	p_responseListeners { onComplete : function(p_commEvent:CommEvent){}
							  			, onCompleteParams : { param1 : value1, ...}
							  			, onIOError : function (p_ioErrorEvent:IOErrorEvent) { } 
							  			}
		 * 
		 * @example
		 * Comm.$Get( "http://www.sitio.com/pagina.aspx", {onComplete:onComplete, onIOError:onError} );
		 */
		static public function $Get(p_url:String, p_responseListeners:Object = null, p_headers:Array = null):void {
			var comm:Comm = new Comm();
			//Comm._PostInit();
			_Calls.push(comm);
			
			comm._Send(p_url, null, p_responseListeners, p_headers, URLRequestMethod.GET);
		}

		/**
		 * Realiza una petición POST a la dirección indicada con los parámetros deseados.
		 * Al terminar correctamente se ejecuta la función onComplete(p_responseListeners) pasándola como parámetro un objeto CommEvent.
		 * A partir de él podemos acceder al texto ($ResponseText), XML ($ResponseXML) o JSON ($ResponseJSON) devuelto.
		 * 
		 * @param	p_url Dirección URL
		 * @param	p_params Parámetros: { "label" : "value", ... }
		 * @param	p_responseListeners { onComplete : function(p_commEvent:CommEvent){}
							  			, onCompleteParams : { param1 : value1, ...}
							  			, onIOError : function (p_ioErrorEvent:IOErrorEvent) { } 
							  			}
		 * @param	p_headers Array de elementos de tipo URLRequestHeader
		 * @param	p_files Array de ficheros: [ { "name":String, "data":ByteArray, "paramName":String }, ... ]
		 * 
		 * @example
		 * 1. Ejemplo básico:
		 * 
		 *   var params:Object = { "email@ideas":"prueba" 
		 *						 ,"nombre@ideas":"prueba"
		 *						 ,"apellidos@ideas":"prueba"
		 *						 ,"fkidcategoria@ideas":0
		 *						};
		 * 
		 *   Comm.$Post("http://www.sitio.com/pagina.aspx",params,{onComplete:onComplete,onIOError:onError});
		 * 
		 * 
		 * 2. Ejemplo más complejo, envía un XML con cabeceras y especificando el tipo del contenido:
		 * 
		 *  var headers : Array = [];
		 *	headers.push( new URLRequestHeader("Authorization", "GoogleLogin auth=" + p_token) );
		 *	headers.push( new URLRequestHeader("GData-Version", "2") );
		 *	headers.push( new URLRequestHeader("X-GData-Key", "key=" + developerKey) );
		 *
		 *	var data : XML = new XML("<?xml version=\"1.0\"?>"
		 *					  + "<entry xmlns=\"http://www.w3.org/2005/Atom\""
		 *					  + " xmlns:media=\"http://search.yahoo.com/mrss/\""
		 *					  + " xmlns:yt=\"http://gdata.youtube.com/schemas/2007\">"
		 *					  + "<media:group>"
		 *					  +   "<media:title type=\"plain\">Title Example "+(new Date()).time+"</media:title>"
		 *					  +    "<media:description type=\"plain\">"
		 *					  +      "Description Example "+(new Date()).time
		 *					  +    "</media:description>"
		 *					  + "</entry>"
		 *					  );
		 *	
		 *	Comm.$Post( "http://gdata.youtube.com/action/GetUploadToken", data, { "onComplete":_UploadVideoMetadata_OnComplete }, headers, null, "application/atom+xml; charset=UTF-8" );
		 * 
		 * 
		 * 3. Ejemplo de envío de ficheros y parámetros multipart:
		 * 
		 *   Comm.$Post( p_url
		 *			  , { "token" : p_token }
		 *			  , { "onComplete" : _UploadVideo_OnComplete, "onIOError" : _UploadVideo_OnIOError }
		 *			  , null
		 *			  , [ { "paramName":"file", "data":_VideoByteArray, "name":"video.flv" } ]
		 *			  );
		 */
		static public function $Post( p_url:String, p_params:Object=null, p_responseListeners:Object=null, p_headers:Array=null, p_files:Array=null, p_contentType:String=null ):void {
			_Post(p_url, p_params, p_responseListeners, p_headers, p_files, p_contentType);
		}

		/**
		 * Realiza una petición para cargar una imagen o un fichero SWF.
		 * Al terminar correctamente se ejecuta la función onComplete(p_responseListeners) pasándola como parámetro un objeto CommEvent.
		 * A partir de él podemos acceder a la imagen ($ResponseBitmap) o al SWF ($ResponseSwf).
		 * 
		 * @param	p_url Dirección URL
		 * @param	p_responseListeners { onComplete : function(p_commEvent:CommEvent){}
							  			, onCompleteParams : Object { param1 : value1, ...}
							  			, onProgress : function (p_progressEvent:ProgressEvent) { }
							  			, onIOError : function (p_ioErrorEvent:IOErrorEvent) { } 
							  			}
		 * @example
		 * 
		 * Comm.$LoadFile("http://www.sitio.com/pagina.aspx?param=value", { onComplete:onComplete, onIOError:onError, onSecurityError:OnError } );
		 */
		static public function $LoadFile(p_url:String, p_responseListeners:Object = null):void {
			var comm:Comm = new Comm();
			//Comm._PostInit();
			_Calls.push(comm);
			
			comm._Load(p_url, p_responseListeners);
		}
		
		static public function $LoadBase64 ( p_base64:String, p_responseListeners:Object = null ) : void {
			var comm:Comm = new Comm();
			//Comm._PostInit();
			_Calls.push(comm);
			
			comm._LoadBase64( p_base64, p_responseListeners );
		}

		static private function _Post(p_url:String, p_params:Object = null, p_responseListeners:Object = null, p_headers:Array = null, p_files:Array=null, p_contentType:String=null):void {
			var comm:Comm = new Comm();
			//Comm._PostInit();
			_Calls.push(comm);
			
			if ( p_files != null ) {
				comm._PostMultipart(p_url, p_responseListeners, p_files, p_params, p_headers);
			}
			else {
				comm._Send(p_url, p_params, p_responseListeners, p_headers, URLRequestMethod.POST, p_contentType);
			}
		}
/*
 * Puede sernos útil para saber el número de peticiones que se estan realizando en un determinado momento
 * 
		static private function _PostFinish():void {
			Comm._PostCount--;
		}

		static private function _PostInit():void {
			Comm._PostCount++;
		}
*/

		private function _Load(p_url:String, p_responseListeners:Object = null):void {
			_Loader = new Loader();
			_Loader_AddListeners(p_responseListeners);
			_Uri = p_url;
			_Loader.load( new URLRequest(_Uri) );
		}
		
		private function _LoadBase64( p_base64:String, p_responseListeners:Object = null ) : void {

			var b64Decoder:Base64Decoder = new Base64Decoder();
			b64Decoder.decode( p_base64 );

			_Loader = new Loader();
			_Loader_AddListeners(p_responseListeners);
_Loader.loadBytes( b64Decoder.toByteArray() );
trace("[WARNING] Comm.Base64._LoadBase64() : A veces se hace loadBytes sin ejecutarse el onComplete sobre todo si esta dentro de bucles.");
		}

		private function _Loader_AddListeners(p_responseListeners:Object):void {
			if ( p_responseListeners.hasOwnProperty("onComplete") ) {
				_Call_OnComplete = p_responseListeners.onComplete;
			}
			if ( p_responseListeners.hasOwnProperty("onCompleteParams") ) {
				_Params = p_responseListeners.onCompleteParams;
			}
			if ( p_responseListeners.hasOwnProperty("onProgress") ) {
				_Call_OnProgress = p_responseListeners.onProgress;
			}
			if ( p_responseListeners.hasOwnProperty("onIOError") ) {
				_Call_OnIOError = p_responseListeners.onIOError;
			}
			
			_Loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _OnLoaderComplete );
			_Loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, _OnLoaderIOError );
			_Loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, _OnLoaderProgress );
		}

		private function _Send( p_url:String, p_params:Object=null, p_responseListeners:Object=null, p_headers:Array=null, p_method:String=URLRequestMethod.POST, p_contentType:String=null ) : void {
			_URLLoader = new URLLoader();

			_Uri = p_url;
			_Uri += ( _Uri.indexOf("?") != -1 ) ? "&" : "?";  // Timestamp
			_Uri += "nocache=" + (new Date()).time;
            var request : URLRequest = new URLRequest( _Uri );
			request.method = p_method;
			if ( p_params is XML ) {
				request.contentType = "application/xml; charset=UTF-8";
				request.data = p_params;
			}
			else {
				request.data = _ParamsToURLVariables(p_params);
			}
			
			if ( p_contentType != null ) {
				request.contentType = p_contentType;
			}

			var header : URLRequestHeader;
			for each ( header in p_headers ) {
				request.requestHeaders.push( header );
			}

			_URLLoader_AddListeners(p_responseListeners);

			_URLLoader.load( request );
		}

		private function _ParamsToURLVariables(p_params:Object):URLVariables {
			var urlVariables : URLVariables = new URLVariables();
			var param : String;
			for  ( param in p_params ) {		
				urlVariables[param] = p_params[param];
			}
			return urlVariables;
		}

		private function _URLLoader_AddListeners(p_responseListeners:Object):void {
			_URLLoader.addEventListener( Event.COMPLETE, _OnLoaderComplete );
			_URLLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, _OnHttpStatus);
			_URLLoader.addEventListener( IOErrorEvent.IO_ERROR, _OnLoaderIOError );

			if ( p_responseListeners.hasOwnProperty("onComplete") ) {
				_Call_OnComplete = p_responseListeners.onComplete;
			}
			if ( p_responseListeners.hasOwnProperty("onCompleteParams") ) {
				_Params = p_responseListeners.onCompleteParams;
			}
			if ( p_responseListeners.hasOwnProperty("onIOError") ) {
				_Call_OnIOError = p_responseListeners.onIOError;
			}
			if ( p_responseListeners.hasOwnProperty("onHttpStatus") ) {
				_Call_OnHttpStatus = p_responseListeners.onHttpStatus;
			}
		}

		private function _OnLoaderComplete(p_event:Event):void {
			// _PostFinish();
			if ( _Call_OnComplete is Function ) {
				var commEvent:CommEvent = new CommEvent(CommEvent.COMPLETE, p_event.currentTarget, _Params);
				_Call_OnComplete(commEvent);
			}
			_Clean();
		}

		private function _OnHttpStatus( p_httpStatusEvent:HTTPStatusEvent ) : void {
			if ( _Call_OnHttpStatus is Function ) {
				_Call_OnHttpStatus(p_httpStatusEvent);
			}
		}

		private function _OnLoaderIOError(p_ioErrorEvent:IOErrorEvent):void {
			//_PostFinish();
			_Clean();
			
			if ( _Call_OnIOError!=null ) {
				_Call_OnIOError(p_ioErrorEvent);
			}
			else {
				throw new Error("Comm._OnLoaderIOError :: Error al intentar cargar la dirección ["+_Uri+"] - Error: "+p_ioErrorEvent);
			}
		}

		private function _OnLoaderProgress(p_progressEvent:ProgressEvent):void {
			if (_Call_OnProgress!=null) {
				_Call_OnProgress(p_progressEvent);
			}
		}

		private function _Clean() : void {
			if ( _Loader != null ) {
				_Loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, _OnLoaderComplete );
				_Loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, _OnLoaderIOError );
				_Loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, _OnLoaderProgress );
				try { 
					_Loader.unloadAndStop(); // para FP 10 o superior 
				}
				catch (e:Error) { 
					try {
						_Loader.unload();
					}
					catch (e:Error) {
					}
				}
				_Loader = null;
			}
			else {
				_URLLoader.close();
				_URLLoader.removeEventListener( Event.COMPLETE, _OnLoaderComplete );
				_URLLoader.removeEventListener( IOErrorEvent.IO_ERROR, _OnLoaderIOError );
				_URLLoader = null;
			}

			var indexCall : Number = _Calls.indexOf(this);
			if ( indexCall != -1 ) {
				_Calls.splice(indexCall, 1);
			}

			ObjectUtil.$CleanMemory();
		}
		
		/**
		 * Finaliza forzosamente todas las peticiones en curso
		 */
		static public function $KillAll () : void {
			var comm : Comm;
			for each ( comm in _Calls ) {
				comm._Clean();
			}
		}

		/*
		 * POST MULTIPART
		 */
		private function _PostMultipart ( p_url:String, p_responseListeners:Object, p_files:Array, p_params:Object=null, p_headers:Array=null ) : void {
			_Uri = p_url;

            var request:URLRequest = new URLRequest( _Uri );
			request.method = URLRequestMethod.POST;
			request.contentType = 'multipart/form-data; boundary=' + _CreateBoundary();
			request.requestHeaders.push( new URLRequestHeader('Cache-Control','no-cache') );
			request.data = _MultipartData(p_files, p_params);
			
			var header : URLRequestHeader;
			for each ( header in p_headers ) {
				request.requestHeaders.push( header );
			}

			_URLLoader = new URLLoader();
			_URLLoader.dataFormat = URLLoaderDataFormat.BINARY;
			_URLLoader_AddListeners(p_responseListeners);
			_URLLoader.load( request );
		}

		private function _CreateBoundary() : String {
			_MultipartBoundary = "";
			
			var f : int;
			for ( f = 0; f < 0x20; f++ ) {
				_MultipartBoundary += String.fromCharCode( int( 97 + Math.random() * 25 ) );
			}

			return _MultipartBoundary;
		}

		private function _MultipartData ( p_files:Array, p_params:Object = null ) : ByteArray {
				var f: int;
				var bytes:String;

				var postData:ByteArray = new ByteArray();
				postData.endian = Endian.BIG_ENDIAN;

				// Parametros
				var paramName : String;
				for( paramName in p_params ) {
						postData = _AddBoundary(postData);
						postData = _AddLinebreak(postData);
						bytes = 'Content-Disposition: form-data; name="' + paramName + '"';
						for ( f = 0; f < bytes.length; f++ ) {
								postData.writeByte( bytes.charCodeAt(f) );
						}
						postData = _AddLinebreak(postData);
						postData = _AddLinebreak(postData);
						postData.writeUTFBytes( p_params[paramName] );
						postData = _AddLinebreak(postData);
				}

				// Ficheros
				var file : Object;
				var fileData : ByteArray;
				for each ( file in p_files ) {
					if ( file.hasOwnProperty("name") && file.hasOwnProperty("data") && file.hasOwnProperty("paramName") ) {
						fileData = file["data"] as ByteArray;
					
						postData = _AddBoundary(postData);
						postData = _AddLinebreak(postData);
						bytes = 'Content-Disposition: form-data; name="'+ file["paramName"] +'"; filename="';
						for ( f = 0; f < bytes.length; f++ ) {
								postData.writeByte( bytes.charCodeAt(f) );
						}
						postData.writeUTFBytes( file["name"] );
						postData = _AddQuotationMark(postData);
						postData = _AddLinebreak(postData);
						bytes = 'Content-Type: application/octet-stream';
						for ( f = 0; f < bytes.length; f++ ) {
								postData.writeByte( bytes.charCodeAt(f) );
						}
						postData = _AddLinebreak(postData);
						postData = _AddLinebreak(postData);
						postData.writeBytes( fileData, 0, fileData.length );
						postData = _AddLinebreak(postData);
					}
					else {
						throw new Error("[ERROR] Comm: Post: Error al construir la petición multipart, los ficheros tienen que tener la siguiente estructura {name:String, data:String, paramName:ByteArray}");
					}
				}

				postData = _AddBoundary(postData);
				postData = _AddDoubleDash(postData);

				return postData;
		}


		private function _AddBoundary ( p:ByteArray ) : ByteArray {
				var l:int = _MultipartBoundary.length;
				p = _AddDoubleDash(p);
				
				var f:int
				for (f = 0; f < l; f++ ) {
					p.writeByte( _MultipartBoundary.charCodeAt( f ) );
				}
				
				return p;
		}

		private function _AddLinebreak(p:ByteArray):ByteArray {
				p.writeShort(0x0d0a);
				return p;
		}

		private function _AddQuotationMark(p:ByteArray):ByteArray {
				p.writeByte(0x22);
				return p;
		}

		private function _AddDoubleDash(p:ByteArray):ByteArray {
				p.writeShort(0x2d2d);
				return p;
		}
	}
	
}